<?php
declare (strict_types=1);

namespace app\course\logic\live;

use app\common\exception\LogicException;
use app\common\library\Queue;
use app\course\logic\base\{Course, CourseSection};
use app\course\model\live\CourseSectionLive as LiveSectionModel;
use app\user\logic\teacher\Teacher;
use app\user\logic\user\Token as UserToken;
use Exception;
use think\facade\Request;
use think\Response;
use think\response\Json;

/**
 * CC接口逻辑层
 * Author 亓官雨树<lucky.max@foxmail.com>
 * Date：   2020-10-27
 */
class LiveBoke extends LiveSectionModel
{

    /**
     * 创建CC云直播间
     */
    public function createZBRoom($param)
    {
        return $this->createCCRoom($param, 'zb');
    }

    /**
     * 创建CC课堂直播间
     */
    public function createKTRoom($param)
    {
        return $this->createCCRoom($param, 'kt');
    }

    /**
     * CC直播间
     */
    public function createCCRoom($param, $type)
    {
        // 参数验证
        $this->getValidate()->scene('create_room')->failException()->check($param);
        // 参数
        $sid = $param['section_id'];
        unset($param['section_id']);
        $mid = request()->mid;
        // 大班课
        $zb = ($type == 'zb');
        // 直播间类型错误验证
        if (!in_array($type, ['zb', 'kt'])) throw new LogicException('参数错误');
        // 检查开播用户身份：讲师身份
        $teacher = app(Teacher::class)->where('user_id', $mid)->findOrEmpty();
        if ($teacher->isEmpty()) throw new LogicException('当前身份无开播权限');
        // 检查房间号是否正确
        $section = app(CourseSection::class)->with(['live_type', 'limit_buy'])->findOrEmpty($sid);

        if (!$section->is_last || $section->course_type != 2 || $section->live_provider != 3) throw new LogicException('未找到课时');
        if ($section->live_type == 1 && !$zb) throw new LogicException('课程信息错误');
        if ($section->live_type == 2 && $zb) throw new LogicException('课程信息错误');
        // 课时名称
        $param['name'] = $param['desc'] = $section->title;
        // 小班课人数
        if (!$zb) {
            // 课程人数限制
            $limit = $section->limit_buy;
            // $param['max_streams'] = $limit == 1 ? 2 : ( floor($limit / 3) + ($limit % 3 ? 1 : 0) ) * 3 + 1;
            $param['max_streams'] = $limit == 1 ? 2 : floor($limit / 3) * 3 + ($limit % 3 ? 4 : 1);
        }
        // 非当前课程讲师
        $course = app(Course::class)->findOrEmpty($section->course_id);
        if ($course->teacher_id != $teacher->id) throw new LogicException('当前身份无开播权限');
        // 存在就返回，不存在就创建
        $data = $this->where('room_no', $sid)->findOrEmpty();
        if ($data->isEmpty()) {

            $roomid = cache('cc_room_' . $sid);
            $boke   = $zb ? new BokeZB((int)$course->id) : new BokeKT((int)$course->id);
            if (!$roomid) {
                $roomid = $boke->createRoom($param);
                cache('cc_room_' . $sid, $roomid);
            }
            $save['room_no']       = $sid;
            $save['live_type']     = $zb ? 2 : 3;
            $save['authtype']      = 0;
            $save['cc_room_id']    = $roomid;
            $save['cc_userid']     = $boke->get_user_id();
            $save['cc_live_ids']   = [];
            $save['cc_record_ids'] = [];
            $save['course_id']     = $section->course_id;
            $save['close_time']    = $section->end_time;

            if ($data = $this::create($save)) {
                cache('cc_room_' . $sid, null);
                return $data;
            } else {
                throw new LogicException('回调失败');
            }
        }
        return $data;
    }

    /**
     * CC云直播/云课堂登录验证
     */
    public function ccRoomEntryAuth(): Json
    {
        $param = Request::post();
        // 参数验证
        $this->getValidate()->scene('room_entry_auth')->failException()->check($param);
        // 参数
        $userid = $param['userid'] ?? '';
        $roomid = $param['roomid'];
        $token  = urldecode($param['viewertoken']);
        $token  = explode(':', $token);
        [$ak, $sk] = $token;
        // 检查课时
        $mod = $this->where('cc_room_id', $roomid);
        $userid && $mod = $mod->where('cc_userid', $userid);
        $data = $mod->field('room_no,course_id')->findOrEmpty();
        if ($data->isEmpty()) return json(['result' => 'fail', 'message' => '课时信息错误']);
        // 检查学员
        $user = app(UserToken::class)->withoutGlobalScope()->where('ak', $ak)->where('sk', $sk)->findOrEmpty();
        if ($user->isEmpty()) return json(['result' => 'fail', 'message' => '用户验证失败']);
        // 讲师端和学生端
        $ctid = app(Course::class)->getCourseTeacherUid($data->course_id);
        // 非讲师检查课程是否加入学习
        if ($ctid != $user->id) {
            // 检查加入课程
            $bought = app(CourseSection::class)->isBuy($data->course_id, $data->room_no, $user->user->id);
            //
            if (!$bought) return json(['result' => 'fail', 'message' => '未加入学习']);
        }

        return json([
            'result'  => 'ok',
            'message' => '验证通过',
            'user'    => [
                'id'   => strval($user->user->id),
                'name' => $user->user->nick_name
            ]
        ]);
    }

    /**
     * CC云直播开始回调
     */
    public function ccLiveStart(): Json
    {
        return $this->_ccLiveStartEnd(1);
    }

    /**
     * CC直播结束回调
     */
    public function ccLiveEnd(): Json
    {
        return $this->_ccLiveStartEnd(2);
    }

    private function _ccLiveStartEnd($stat): Json
    {
        //
        $param = Request::get();
        $ok    = json(['result' => 'OK']);
        $fail  = json(['result' => 'FAIL']);
        //
        $type   = urldecode($param['type']);
        $liveId = urldecode($param['liveId']);
        $userId = urldecode($param['userId']);
        $roomId = urldecode($param['roomId']);
        if ($stat != $type) return $ok;
        // 推送记录同步任务
        if ($stat == 2) $this->pushSyncJob($roomId, $liveId);
        //
        $data = $this->where('cc_userid', $userId)->where('cc_room_id', $roomId)->findOrEmpty();
        $ids  = $data->cc_live_ids;
        if (is_array($ids)) {
            if (!in_array($liveId, $ids)) {
                $ids[] = $liveId;
                $data->cc_live_ids = $ids;
            }
        } else {
            $data->cc_live_ids = [$liveId];
        }

        return $data->save() ? $ok : $fail;
    }

    /**
     * CC直播录制回调
     */
    public function ccReplayCallback(): Json
    {
        //
        $param = Request::get();
        $ok    = json(['result' => 'OK']);
        $fail  = json(['result' => 'FAIL']);
        //
        $type       = urldecode($param['type']);
        $sourcetype = urldecode($param['sourcetype']);
        $userId     = urldecode($param['userId']);
        $roomId     = urldecode($param['roomId']);
        $recordId   = urldecode($param['recordId']);
        $liveId     = urldecode($param['liveId']);
        $status     = urldecode($param['recordStatus']);
        $replayUrl  = urldecode($param['replayUrl']);
        //
        if (103 != $type || $sourcetype != 0 || $status != 10) return $ok;
        //
        $data = $this->where('cc_userid', $userId)->where('cc_room_id', $roomId)->findOrEmpty();
        if ($data->isEmpty()) return $ok;
        //
        $lids   = $data->cc_live_ids;
        $rids   = $data->cc_record_ids;
        $needle = array_search($liveId, $lids);
        $prev   = $lids[$needle - 1] ?? '';
        $next   = $lids[$needle + 1] ?? '';

        if (!$data->cc_replay_id) {
            $data->cc_replay_id  = $recordId;
            $rids[$liveId]       = $recordId;
            $data->cc_record_ids = $rids;
            $data->cc_replay_url = $replayUrl;
            return $data->save() ? $ok : $fail;
        }

        try {
            // 实例CC直播类
            $zb = new BokeZB();
            // 回放模板不一致返回
            $lastIn = $zb->replayInfo($data->cc_replay_id);
            $thisIn = $zb->replayInfo($recordId);
            if ($lastIn->templateType != $thisIn->templateType) return $ok;

            $recordids = $recordId;
            $idarr[]   = $liveId;
            // 保存cc_record_ids
            $rids[$liveId]       = $recordId;
            $data->cc_record_ids = $rids;
            if (!$data->save()) return $fail;
            // 不存在连续性时，返回成功
            if (!isset($rids[$prev]) && !isset($rids[$next])) return $ok;
            // 检查前后回放是否存在，存在即合并
            if (isset($rids[$prev])) {
                $recordids = $rids[$prev] . ',' . $recordids;
                array_unshift($idarr, $prev);
            }
            if (isset($rids[$next])) {
                $recordids .= ',' . $rids[$next];
                $idarr[] = $next;
            }
            //
            $cacheids = str_replace(',', '_', $recordids);
            //
            if ($cacheid = cache('cc_replay_merge_' . $cacheids)) {
                $newrid = $cacheid;
            } else {
                // 合并
                $record = $zb->mergeReplay(['roomid' => $roomId, 'recordids' => $recordids]);
                $newrid = $record['recordid'];
                $newlid = $record['liveid'];
                // 合并成功后缓存
                cache('cc_replay_merge_' . $cacheids, $newrid);
                // 合并成功后替换
                $newlids = array_map(function ($lid) use ($idarr, $newlid) {
                    return in_array($lid, $idarr) ? $newlid : $lid;
                }, $lids);
                $newlids = array_unique($newlids);

                foreach ($idarr as $lid) {
                    if (isset($rids[$lid])) unset($rids[$lid]);
                }
                $rids[$newlid] = $newrid;

                $data->cc_live_ids   = $newlids;
                $data->cc_record_ids = $rids;
            }
            // 直播间信息
            $room = $zb->roomInfo($data->cc_room_id);

            $data->cc_replay_id = $newrid;

            if (!$data->save()) throw new LogicException('合并存储失败');
            // 编辑回放
            $up = [
                'title'    => $room->name,//'合并回放：'.$param['recordids'],
                'desc'     => $room->desc,//.''.$param['recordids'],
                'recordid' => $newrid,
            ];
            $zb->upReplay($up);
            // 回调成功回应
            return $ok;
        } catch (LogicException $e) {
            // 回调失败回应
            return $fail;
        }
    }

    /**
     * CC云课堂开始结束直播回调
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-11-11
     *
     * @return Response
     * @throws Exception
     */
    public function ccKTLive(): Response
    {
        $data   = Request::post();
        $roomId = $data['roomid'] ?? '';
        $liveId = $data['liveid'] ?? '';
        if (!$roomId || $liveId) {
            return Response::create(['result' => 'FAIL'], 'json')->code(200);
        }

        $type = $data['type'];
        if ($type === 'end_live') {
            // 直播结束，推送同步观看记录任务
            $this->pushSyncJob($roomId, $liveId, 2, 5);
        }

        return Response::create(['result' => 'OK'], 'json')->code(200);
    }

    /**
     * CC云课堂录制回调
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-11-11
     *
     * @return Response
     */
    public function ccKTReplay(): Response
    {
        return Response::create(['result' => 'OK'], 'json')->code(200);
    }

    /**
     * 小班课自动登录链接
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 2021-11-11
     *
     * @param $param
     * @return mixed
     */
    public function autoUri($param)
    {
        $kt = new BokeKT();
        return $kt->getAutoLoginUri($param);
    }

    /**
     * 推送直播同步任务
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2021/11/12
     *
     * @param     $roomId
     * @param     $liveId
     * @param int $liveType 1 大班课(云直播) 2 小班课(云课堂)
     * @param int $waitTime
     */
    private function pushSyncJob($roomId, $liveId, int $liveType = 1, int $waitTime = 1800)
    {
        $job = $liveType === 1 ? 'YZBViewRecord' : 'YKTViewRecord';
        // 推送任务
        Queue::push($job, ['live_id' => $liveId, 'room_id' => $roomId], $waitTime);
    }

    /**
     * 获取小班课回放链接
     */
    public function getKTRecordUrl($recordid)
    {
        $zb = new BokeZB();
        return $zb->getRecordUrl($recordid);
    }

    /**
     * 获取小班课全景回放链接
     */
    public function getKTFullRecordUrl($param)
    {
        $kt = new BokeKT();
        return $kt->getFullRecordUrl($param);
    }

}
